#pragma region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#pragma endregion

#include "stdafx.h"

Configuration Configuration::_current;

const wchar_t* OshMixerXPath = L"/configuration/oshMixer";
const wchar_t* RegisterGraphXPath = L"@registerGraph";
const wchar_t* MultipleMixersXPath = L"@allowMultipleMixers";
const wchar_t* EnabledAppNamesXPath = L"applications/application[not(@enabled) or @enabled='true']/@name";

const wchar_t* VMRAppXPathPattern = L"/configuration/oshMixer/applications/application[@name='%s']";
const wchar_t* VMRAppForceMixingModeXPath = L"@forceMixingMode";
const wchar_t* VMRAppNumberOfStreamsXPath = L"@numberOfStreams";
const wchar_t* VMRAppCanReconnectXPath = L"@canReconnect";
const wchar_t* FilterGraphRemoveFilterXPath = L"filterGraph/removeFilter";
const wchar_t* FilterGraphInsertFilterXPath = L"filterGraph/insertFilter";


Configuration::Configuration() : _initialized(false), _appConfigInitialized(false), _registerGraph(false), _multipleMixers(false)
{
}

Configuration::~Configuration()
{

}

HRESULT Configuration::Initialize()
{
	CString filePath;
	HRESULT hr = GetConfigurationFilePath(filePath);
	if(FAILED(hr)) return hr;

	ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
		"Using xml file: %S\r\n", filePath.GetBuffer());

	CComPtr<IXMLDOMDocument> doc;
	hr = GetXmlConfig(&doc);
	if(FAILED(hr)) return hr;

	CComPtr<IXMLDOMNode> oshMixerNode;
	hr = doc->selectSingleNode(CComBSTR(OshMixerXPath), &oshMixerNode);
	if(FAILED(hr)) return hr;
	if(hr == S_FALSE) return E_FAIL;

	// Read RegisterGraph setting
	_registerGraph = false;
	hr = ReadSetting(oshMixerNode, RegisterGraphXPath, _registerGraph, true);
	if(FAILED(hr)) return hr;
	ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
		"%S/%S: %s\r\n", OshMixerXPath, RegisterGraphXPath, _registerGraph ? "yes" : "no");

	// Read MultipleMixers setting
	_multipleMixers = false;
	hr = ReadSetting(oshMixerNode, MultipleMixersXPath, _multipleMixers, true);
	if(FAILED(hr)) return hr;
	ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
		"%S/%S: %s\r\n", OshMixerXPath, MultipleMixersXPath, _multipleMixers ? "yes" : "no");

	hr = ReadSettings(oshMixerNode, EnabledAppNamesXPath, _enabledApps);
	if(FAILED(hr)) return hr;
	ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
		"Found %d enabled apps\r\n", _enabledApps.GetSize());
	for(int i = 0; i < _enabledApps.GetSize(); i++)
	{
		ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info, "\t%S\r\n", _enabledApps[i]);
		_enabledApps[i].ToLower();
	}

	_initialized = true;
	return S_OK;
}

HRESULT Configuration::GetXmlConfig(IXMLDOMDocument** document) const
{
	if(document == NULL) return E_INVALIDARG;
	*document = NULL;

	CString filePath;
	HRESULT hr = GetConfigurationFilePath(filePath);
	if(FAILED(hr)) return hr;

	CComPtr<IXMLDOMDocument> doc;
	hr = doc.CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER);
	if(FAILED(hr)) return hr;

	hr = doc->put_async(VARIANT_FALSE);
	if(FAILED(hr)) return hr;

	VARIANT_BOOL isSuccessful = VARIANT_FALSE;
	hr = doc->load(CComVariant(filePath), &isSuccessful);
	if(FAILED(hr)) return hr;
	if(isSuccessful == VARIANT_FALSE) return E_FAIL;

	*document = doc.Detach();
	return S_OK;
}

AppConfig& Configuration::GetCurrentAppConfig()
{
	if(_appConfigInitialized)
		return _appConfig;

	CString filePath;
	HRESULT hr = GetConfigurationFilePath(filePath);
	if(SUCCEEDED(hr))
	{
		CString appName = GetCurrentAppName();
		if(!appName.IsEmpty())
		{
			CComPtr<IXMLDOMDocument> doc;
			hr = GetXmlConfig(&doc);
			if(FAILED(hr)) return _appConfig;

			CString appXPath;
			appXPath.AppendFormat(VMRAppXPathPattern, appName.GetBuffer());

			CComPtr<IXMLDOMNode> appNode;
			hr = doc->selectSingleNode(CComBSTR(appXPath), &appNode);
			if(hr != S_OK) return _appConfig;

			ReadSetting(appNode, VMRAppForceMixingModeXPath, _appConfig.ForceMixingMode, true);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
				"%S\\%S: %s\r\n", appName, VMRAppForceMixingModeXPath, _appConfig.ForceMixingMode ? "yes" : "no");

			ReadSetting(appNode, VMRAppNumberOfStreamsXPath, _appConfig.NumberOfStreams, true);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
				"%S\\%S: %d\r\n", appName, VMRAppNumberOfStreamsXPath, _appConfig.NumberOfStreams);

			ReadSetting(appNode, VMRAppCanReconnectXPath, _appConfig.CanReconnect, true);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
				"%S\\%S: %s\r\n", appName, VMRAppCanReconnectXPath, _appConfig.CanReconnect ? "yes" : "no");
			
			// Parses filter graph commands
			ReadFilterGraphCommands(appNode, _appConfig);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Info,
				"Parsed %d remove command(s) and %d insert command(s).\r\n",
				_appConfig.RemoveFilters.GetSize(),
				_appConfig.InsertFilters.GetSize());
		}
	}
	_appConfigInitialized = true;
	return _appConfig;
}

CString& Configuration::GetCurrentAppName()
{
	if(_appName.IsEmpty())
	{
		wchar_t appFilePath[MAX_PATH];
		DWORD len = ::GetModuleFileName(NULL, appFilePath, MAX_PATH);
		if(len > 0)
		{
			wchar_t* appName = appFilePath;

			wchar_t* lastSlashPosition = ::wcsrchr(appFilePath, '\\');
			if(lastSlashPosition != NULL)
				appName = lastSlashPosition + 1;

			_appName = appName;
		}
	}
	return _appName;
}

HRESULT Configuration::IsCurrentAppEnabled()
{
	return IsAppEnabled(GetCurrentAppName());
}

HRESULT Configuration::IsAppEnabled(const wchar_t* appName)
{
	if(!_initialized) return E_UNEXPECTED;
	if(appName == NULL) return E_INVALIDARG;

	CComBSTR appNameStr(appName);
	appNameStr.ToLower();

	bool isAppEnabled = false;
	if(_enabledApps.GetSize() > 0)
		isAppEnabled = (_enabledApps.Find(appNameStr) != -1);

	return isAppEnabled ? S_OK : S_FALSE;
}

HRESULT Configuration::ReadFilterGraphCommands(IXMLDOMNode* appNode, AppConfig& appConfig) const
{
	// Reads removeFilter commands
	CComPtr<IXMLDOMNodeList> removeCommands;
	HRESULT hr = appNode->selectNodes(CComBSTR(FilterGraphRemoveFilterXPath), &removeCommands);
	if(FAILED(hr)) return hr;

	long length;
	hr = removeCommands->get_length(&length);
	if(FAILED(hr)) return hr;

	for(long i = 0; i < length; i++)
	{
		CComPtr<IXMLDOMNode> removeFilterNode;
		hr = removeCommands->get_item(i, &removeFilterNode);
		if(FAILED(hr)) return hr;

		hr = ReadFilterGraphRemoveCommand(removeFilterNode, appConfig);
		if(FAILED(hr)) return hr;

		break;
	}
	
	// Reads insertFilter commands
	CComPtr<IXMLDOMNodeList> insertCommands;
	hr = appNode->selectNodes(CComBSTR(FilterGraphInsertFilterXPath), &insertCommands);
	if(FAILED(hr)) return hr;

	hr = insertCommands->get_length(&length);
	if(FAILED(hr)) return hr;

	for(long i = 0; i < length; i++)
	{
		CComPtr<IXMLDOMNode> insertFilterNode;
		hr = insertCommands->get_item(i, &insertFilterNode);
		if(FAILED(hr)) return hr;

		hr = ReadFilterGraphInsertCommand(insertFilterNode, appConfig);
		if(FAILED(hr)) return hr;
	}
	
	return S_OK;
}

HRESULT Configuration::ReadFilterGraphRemoveCommand(IXMLDOMNode* removeFilterNode, AppConfig& appConfig) const
{
	RemoveFilterCommand command;
	HRESULT hr = ReadSetting(removeFilterNode, L"@id", command.ClassId, false);
	if(FAILED(hr)) return hr;

	appConfig.RemoveFilters.Add(command);
	return S_OK;
}

HRESULT Configuration::ReadFilterGraphInsertCommand(IXMLDOMNode* insertFilterNode, AppConfig& appConfig) const
{
	InsertFilterCommand command;
	HRESULT hr = ReadSetting(insertFilterNode, L"@id", command.ClassId, false);
	if(FAILED(hr)) return hr;

	hr = ReadSetting(insertFilterNode, L"@after", command.AfterFilterClassId, false);
	if(FAILED(hr)) return hr;

	hr = ReadSetting(insertFilterNode, L"@before", command.BeforeFilterClassId, false);
	if(FAILED(hr)) return hr;

	appConfig.InsertFilters.Add(command);
	return S_OK;
}

HRESULT Configuration::ReadSettings(IXMLDOMNode* node, const wchar_t* xpath, CSimpleArray<CComBSTR>& values) const
{
	CComPtr<IXMLDOMNodeList> nodes;
	HRESULT hr = node->selectNodes(CComBSTR(xpath), &nodes);
	if(FAILED(hr)) return hr;
	
	long length;
	hr = nodes->get_length(&length);
	if(FAILED(hr)) return hr;

	for(long i = 0; i < length; i++)
	{
		CComPtr<IXMLDOMNode> selectedNode;
		hr = nodes->get_item(i, &selectedNode);
		if(FAILED(hr)) return hr;

		CComBSTR value;
		hr = ReadSetting(selectedNode, NULL, value, false);
		if(FAILED(hr)) return hr;

		values.Add(value);
	}
	return S_OK;
}

HRESULT Configuration::ReadSetting(IXMLDOMNode* node, const wchar_t* xpath, CComBSTR& value, bool isOptional) const
{
	CComPtr<IXMLDOMNode> selectedNode;
	if(xpath == NULL)
		selectedNode = node;
	else
	{
		HRESULT hr = node->selectSingleNode(CComBSTR(xpath), &selectedNode);
		if(FAILED(hr)) return hr;
		if(hr == S_FALSE) return isOptional ? S_FALSE : E_FAIL;
	}

	CComPtr<IXMLDOMAttribute> attribute;
	HRESULT hr = selectedNode->QueryInterface(&attribute);
	if(SUCCEEDED(hr))
	{
		CComVariant var;
		hr = attribute->get_value(&var);
		if(FAILED(hr)) return hr;

		if(var.vt == VT_BSTR)
			value = var.bstrVal;
		else
			value.Empty();
		return S_OK;
	}
	
	return selectedNode->get_text(&value);
}

HRESULT Configuration::ReadSetting(IXMLDOMNode* node, const wchar_t* xpath, bool& value, bool isOptional) const
{
	CComBSTR strValue;
	HRESULT hr = ReadSetting(node, xpath, strValue, isOptional);
	if(FAILED(hr)) return hr;
	if(hr == S_FALSE) return isOptional ? S_FALSE : E_FAIL;

	hr = strValue.ToLower();
	if(FAILED(hr)) return hr;

	value = !(strValue == L"0" || strValue == L"false" || strValue == L"no");
	return S_OK;
}

HRESULT Configuration::ReadSetting(IXMLDOMNode* node, const wchar_t* xpath, int& value, bool isOptional) const
{
	CComBSTR strValue;
	HRESULT hr = ReadSetting(node, xpath, strValue, isOptional);
	if(FAILED(hr)) return hr;
	if(hr == S_FALSE) return isOptional ? S_FALSE : E_FAIL;

	hr = strValue.ToLower();
	if(FAILED(hr)) return hr;

	if(swscanf_s(strValue, L"%d", &value, sizeof(value)) == 1)
		return S_OK;

	return E_FAIL;
}

HRESULT Configuration::ReadSetting(IXMLDOMNode* node, const wchar_t* xpath, GUID& value, bool isOptional) const
{
	CComBSTR strValue;
	HRESULT hr = ReadSetting(node, xpath, strValue, isOptional);
	if(FAILED(hr)) return hr;
	if(hr == S_FALSE) return isOptional ? S_FALSE : E_FAIL;

	return DirectShowUtility::GuidFromString(strValue, value);
}

HRESULT Configuration::GetConfigurationFilePath(CString& filePath) const
{
	wchar_t moduleFilePath[MAX_PATH];
	DWORD len = ::GetModuleFileName(_AtlBaseModule.GetModuleInstance(), moduleFilePath, MAX_PATH);
	if(len == 0)
		return AtlHresultFromLastError();

	wchar_t* dotPosition = ::wcsrchr(moduleFilePath, '.');
	if(dotPosition != NULL)
		*dotPosition = '\0';

	filePath = moduleFilePath;
	filePath.Append(L".config");
	return S_OK;
}


/* static */ Configuration& Configuration::Current()
{
	if(!_current._initialized)
		_current.Initialize();

	return _current;
}

